﻿using SharpDepend.Datatypes;
using System;
using System.Collections.Generic;
using System.Linq;
using ZquenceStudio3Host.DspPlugins.Synthesizers.Nodesizer.Engine.GUI.Controls;
using ZquenceStudio3Host.DspPlugins.Synthesizers.Nodesizer.Engine.Modules.Container;
using ZquenceStudio3Host.Commands.BuiltInCommands;
using ZquenceStudio3Host.DspPlugins.Synthesizers.Nodesizer.Engine.GUI;
using DefaultDspPlugins.UI.Common.Modulizer.Engine.GUIv2.Editors;

namespace ZquenceStudio3Host.DspPlugins.Synthesizers.Nodesizer.Engine.Commands
{
    class DuplicateModuleCommand : BaseCommand
    {
        private IModuleContainer mParentModule;
        private NodisizerAreaHandler mNodisizerArea;
        private List<BaseModule> mModules;
        private List<BaseModule> mNewModules;
        private BaseModulizerUI mNodisizer;
        private List<AddModuleCommand> mAddCommands = new List<AddModuleCommand>();
        private bool mOnce;
        private ModulizerTrackArea mNodisizerAreaV2;
        private ModulizerUIv2 mNodisizerV2;

        public override object[] ReferenceObjects
        {
            get
            {
                List<object> objects = new List<object>();
                foreach (var module in mModules)
                {
                    objects.Add(module);
                }
                foreach (var newModule in mNewModules)
                {
                    objects.Add(newModule);
                }

                return objects.ToArray();
            }
        }

        public List<BaseModule> NewModules
        {
            get { return mNewModules; }
        }
        
        public DuplicateModuleCommand(NodisizerAreaHandler nodisizerArea, BaseModulizerUI nodisizer, IEnumerable<BaseModule> modules)
        {
            mNodisizerArea = nodisizerArea;
            mNodisizer = nodisizer;
            mModules = modules.ToList();

            InitParent(modules);
        }

        public DuplicateModuleCommand(ModulizerTrackArea nodisizerArea, ModulizerUIv2 mNodisizer1, IEnumerable<BaseModule> modules)
        {
            mNodisizerAreaV2 = nodisizerArea;
            mNodisizerV2 = mNodisizer1;
            mModules = modules.ToList();

            InitParent(modules);
        }

        private void InitParent(IEnumerable<BaseModule> modules)
        {
            if (mModules.Count == 0)
            {
                throw new ArgumentException("There must be at least one module to duplicate.");
            }

            foreach (var module in modules)
            {
                if (mParentModule == null)
                {
                    mParentModule = module.ParentModule as IModuleContainer;

                    if (mParentModule == null)
                    {
                        throw new Exception("Parent module must be an IModuleContainer.");
                    }
                }
                else
                {
                    if (mParentModule != module.ParentModule)
                    {
                        throw new Exception("Modules that will be duplicated must contains in the same module parent.");
                    }
                }
            }
        }

        protected override void Execute()
        {
            // Create new pattern for the new unique reference for the first time.
            // And it will merge add command and remove command for the referenes.
            if (mNewModules == null)
            {
                mNewModules = new List<BaseModule>(mModules.Count);

                // Pre calc first and last reference positions.
                double firstReferenceTick = double.MaxValue;
                double lastReferenceTick = double.MinValue;
                foreach (var module in mModules)
                {
                    firstReferenceTick = Math.Min(module.GuiPosition.X, firstReferenceTick);
                    lastReferenceTick = Math.Max(module.GuiPosition.X + 40, lastReferenceTick); // TODO: Lägg till Width också.
                }
                double moveForward = lastReferenceTick - firstReferenceTick;

                foreach (var module in mModules)
                {
                    BaseModule copiedModule = null;

                    if (mNodisizerV2 != null)
                    {
                        // Create new reference.
                        copiedModule = module.Clone(mNodisizerV2.mModuleManager);
                    }
                    else
                    {
                        // Create new reference.
                        copiedModule = module.Clone(mNodisizer.Modulizer.mModuleManager);
                    }
                    
                    // Set position.
                    Vector4 bound = module.GuiPosition;
                    bound.X += moveForward;

                    mNewModules.Add(copiedModule);

                    mParentModule.AddSubModule(copiedModule);

                    AddModuleCommand addCommand = null;

                    if (mNodisizerV2 != null)
                    {
                        addCommand = new AddModuleCommand(mNodisizerV2, bound, copiedModule);
                    }
                    else
                    {
                        addCommand = new AddModuleCommand(mNodisizer, bound, copiedModule);
                    }
                    mAddCommands.Add(addCommand);
                    Merge(addCommand);
                }
            }
        }
        
        protected override void OnExecuted()
        {
            if (mOnce)
            {
                for (int i = 0; i < mNewModules.Count; i++)
                {
                    BaseModule module1 = mNewModules[i];
                    BaseModule module2 = mModules[i];

                    if (mNodisizerV2 != null)
                    {
                        module1.ImitateConnection(mNodisizerV2.mModuleManager, module2);
                    }
                    else
                    {
                        module1.ImitateConnection(mNodisizer.Modulizer.mModuleManager, module2);
                    }
                }
            }

            mOnce = true;

            // Select.
            if (mNodisizerV2 != null)
            {
                mNodisizerV2.TrackArea.Editor.SelectorHandler.ClearSelection();
                mNodisizerV2.GoInto(mModules[0].ParentModule);
                mNodisizerV2.UpdateModuleUIUp(mModules[0].ParentModule);
                mNodisizerV2.UpdateParameterStates();
            }
            else
            {
                mNodisizerArea.Editor.SelectorHandler.ClearSelection();
                mNodisizer.RebuildSynthGraphModuleParent(mModules[0]);
                mNodisizer.UpdateParameterStates();
            }
        }

        protected override void UnExecute()
        {
            foreach (var copiedModule in mNewModules)
            {
                copiedModule.ClearAllConnections();
            }
        }

        protected override void OnUnExecuted()
        {
            if (mNodisizerV2 != null)
            {
                mNodisizerV2.TrackArea.Editor.SelectorHandler.ClearSelection();
                mNodisizerV2.GoInto(mModules[0].ParentModule);
                mNodisizerV2.UpdateModuleUIUp(mModules[0].ParentModule);
                mNodisizerV2.UpdateParameterStates();
            }
            else
            {
                mNodisizerArea.Editor.SelectorHandler.ClearSelection();
                mNodisizer.RebuildSynthGraphModuleParent(mModules[0]);
                mNodisizer.UpdateParameterStates();
            }
        }

        public override string ToString()
        {
            return "Duplicate modules.";
        }
    }
}
